feat(channel list): add persistent channel sort toggle#1505
Conversation
Pull request was converted to draft
wesbillman
left a comment
There was a problem hiding this comment.
Review (Brain, agent review on Wes's behalf) — one product question, otherwise sound
The question worth settling before merge: the PR body and the implementation disagree about scope. The body says "This adds one persisted sidebar sort preference… and applies it within each existing grouping," and the screenshots show a single dropdown on the Channels header. The code actually ships independent per-group preferences — a separate ↑↓ dropdown on Starred, every custom section, Channels, Forums, and DMs, each with its own saved mode (e2e test 03 explicitly asserts group independence). Per-group is defensible, but it's 5+ header controls instead of 1, and it's not what the description sells. Which one is the product intent? If "one preference," the store/UI should collapse to a single mode; if per-group, the body needs rewriting to match.
Technical review — no blockers:
- Default A–Z genuinely preserves today's order:
useChannelsQuery.sortChannelsalready sorts by name, and the new comparator only adds an id tie-break. DMs keep the resolved-label sort in alpha mode. ✅ - Persistence is properly scoped pubkey+relay via the same
normalizeRelayUrlpattern aschannelSectionsStorage, with payload sanitization and cross-windowstoragesync. ✅ - Comparators are pure, non-mutating, deterministic (recency → name → id), quiet channels sink alphabetically — all unit-pinned. ✅
- CI fully green, including the new smoke spec.
Minor, non-blocking:
- Deleted custom sections leave orphaned
section:<id>keys in the storedgroupsmap forever — harmless but a slow localStorage leak. A one-line prune against live section ids on write would close it. - In "Recent" mode, ordering freshness is bounded by
channelsQuery's 60s refetch/invalidations, so the sidebar can lag reality by up to a minute — and when it does update, rows move under the cursor. Worth knowing, not worth blocking.
|
🤖 Thanks for the review, Wes — I pushed updates addressing the feedback:
CI was green on the updated head before the latest base conflict; I’m resolving that conflict now. |
Adds a user-toggleable sort preference for the left-nav channel lists. One persisted preference (localStorage, scoped by pubkey + relay like channel sections) applied inside every grouping - Starred, each custom section, unassigned Channels, Forums, and DMs - without changing grouping boundaries, section assignments, or drag-and-drop. Recent sorts by lastMessageAt (newest first); channels with no parseable activity sink to the bottom alphabetically. A-Z is the default so existing ordering is preserved until the user opts in. The control is a small sort dropdown in the Channels section header action cluster, mirroring the inbox filter dropdown pattern. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Replaces the single sidebar-wide sort preference with an independent
per-group map persisted as {version, groups} under the same
pubkey+relay-scoped localStorage key. Group keys are starred, channels,
forums, dms, and section:<sectionId> (section IDs are stable UUIDs, so
prefs survive rename/reorder). Any unset group defaults to A-Z.
Every group header - Starred, each custom section, Channels, Forums,
and DMs - now gets its own sort dropdown at the front of its action
cluster. Adds a focused e2e spec covering default ordering, Recent
reorder with reload persistence, independent group preferences, and
the DM group trigger.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Deleted custom sections previously left their section:<id> sort modes in the persisted store forever — a harmless but unbounded localStorage leak. Writes now strip entries whose section id is no longer live, keeping the fixed group keys (starred/channels/forums/dms) untouched. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Re-applies the section-actions work from #1424 (silently clobbered by #1428) and extends it: a single reusable SectionActionsMenu component now renders the rightmost action for every sidebar section (Channels, Forums, Direct messages, Starred, and custom sections). - Fold the standalone Sort dropdown (#1505) into the ⋮ menu as a Sort radio group; delete ChannelSortDropdown. - Move Browse channels / New channel / New forum / New message into the menu; drop the standalone header icon buttons. - Remove the visible GripVertical grip from custom sections (drag handle props retained, so drag-to-reorder still works). - deferMenuAction (setTimeout 0) avoids the modal/pointer-events freeze when a menu item opens a dialog. - Keep the #1516 emoji/icon picker on custom sections. Updates e2e helpers (openCreateChannelDialog / openNewDirectMessageDialog) and specs (channel-sort, channels, identity-archive-hide) to drive the new menu. Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Category: new-feature
User Impact: Users can choose, per sidebar group, whether channels are ordered alphabetically or by recent activity.
Problem: The left nav had one fixed channel ordering, which made it harder to scan either by name or by what changed most recently depending on the user's current workflow.
Solution: This adds independent persisted Recent / A–Z sort preferences for each sidebar grouping — Starred, every custom section, Channels, Forums, and DMs — each with its own header control. Every group defaults to A–Z to preserve today's behavior, and group boundaries stay untouched. Preferences are stored per pubkey + relay; sort modes for deleted custom sections are pruned on write so they don't accumulate in localStorage.
File changes
desktop/src/features/sidebar/lib/channelSortPreference.ts
Introduces the localStorage-backed per-group sort store (versioned
groupsmap keyed bystarred/channels/forums/dms/section:<id>), the shared channel sorting helper, andstripOrphanedSectionModeswhich prunes stalesection:<id>entries for deleted custom sections.desktop/src/features/sidebar/lib/channelSortPreference.test.mjs
Covers payload sanitization, per-group defaults and independence, orphaned-section pruning, recent ordering, missing/unparseable activity, and deterministic tie-breaks.
desktop/src/features/sidebar/lib/dmSidebarSort.ts
Makes DM sorting mode-aware without changing the resolved-label ordering users already see by default.
desktop/src/features/sidebar/lib/dmSidebarSort.test.mjs
Extends DM sorting tests so DMs keep their existing A–Z behavior while supporting recent activity ordering.
desktop/src/features/sidebar/lib/useChannelSortPreference.ts
Adds a hook for reading and updating each group's sort mode, syncing across windows via the
storageevent, and pruning deleted-section entries on write using the live section ids.desktop/src/features/sidebar/ui/AppSidebar.tsx
Wires each grouping's own sort preference into its bucket (starred, per-section, unassigned channels, forums, DMs) and passes live section ids into the preference hook; section assignment and drag-and-drop paths are untouched.
desktop/src/features/sidebar/ui/ChannelSortDropdown.tsx
Adds the compact Recent / A–Z dropdown control rendered in each group's header action cluster using the existing dropdown radio pattern.
desktop/src/features/sidebar/ui/CustomChannelSection.tsx
Lets custom section headers host their own sort control without one-off layout behavior.
Reproduction Steps
Screenshots/Demos
Tight crop of the header — the ↑↓ is the ingress (each group header has its own):

Dropdown open (default A–Z):

Notes
channelsQuery's ~60s foreground refetch (plus mutation/reconnect invalidations); incoming messages for already-known channels don't directly patchlastMessageAt, so the order can lag up to that refresh window.Validation
biome check— clean on sidebar feature (repo-wide: 4 pre-existing warnings inpulse/hooks.ts)pnpm typecheck— cleanpnpm test— 1605 pass / 0 fail (includes 4 new orphaned-section pruning tests)